;; The following code is from
;; [[https://www.draketo.de/software/guile-capture-stdout-stderr.html]].

;; Comments and some formatting by me.

;; Related links:

;; https://www.gnu.org/software/guile/manual/html_node/Pipes.html
;; https://www.gnu.org/software/guile/manual/guile.html#Ports-and-File-Descriptors

;; This is an example of how you can write a procedure, which allows you to run
;; a shell command from GNU Guile and capture not only its stdout output in a
;; string, but also its stderr output in a string. This can be useful, if you
;; need to parse the output of both stdout and stderr.

(import
 (ice-9 rdelim)
 (ice-9 popen)
 (rnrs io ports))


;; The procedure is named in the same manner as call-with-output-string or
;; call-with-output-file for example.
(define (call-command-with-output-error-to-string cmd)
  ;; (pipe) creates 2 linked ports as a pair. The car is an input port and cdr
  ;; is an output port. What goes in to the input port comes out at the output
  ;; port.
  (let* ([err-cons (pipe)]
         [port
          ;; Set up a scope in which the stderr will be written to a given port.
          (with-error-to-port (cdr err-cons)
            (λ ()
              ;; Run a command in a subprocess, with mode OPEN_READ, using the
              ;; open-pipe procedure. The open-pipe procedure runs the shell
              ;; command using "/bin/sh -c".

              ;; Return an input pipe, because it is open-pipe with OPEN_READ.

              ;; The command is already run here. The output of the command will
              ;; be available from the input pipe.

              ;; See:
              ;; [[https://www.gnu.org/software/guile/manual/html_node/Pipes.html]]
              (open-input-pipe cmd)))]
         ;; Set the input port to blocking, if there is more than (* 1024 1024
         ;; 16) bytes.
         [_ (setvbuf (car err-cons)
                     'block
                     ;; 16 megabytes (byte * 1024 -> kilobyte, kilobyte * 1024
                     ;; -> megabyte, megabyte * 16 -> 16 megabytes)
                     (* 1024 1024 16))]
         ;; Read everything from port until a character of the given string is
         ;; encountered. No character is in the string, so everything is read
         ;; from the port, until EOF is encountered.
         [result (read-delimited "" port)])
    ;; We already have the result from the command at this point. All that is
    ;; left to do is to output the result appropriately.

    ;; Make sure the port is properly closed. Nothing more shall be written to
    ;; the port, as the command has already been sent and run.
    (close-port (cdr err-cons))

    ;; Return 2 values, one is the stdout output of the command and one is the
    ;; strerr output of the command.
    (values result
            ;; read-delimited from (ice-9 rdelim) reads from the port until a
            ;; character from the string "" is encountered or stops at
            ;; EOF. Since "" is empty, it means, that all will be read until EOF
            ;; is encountered.
            (read-delimited "" (car err-cons)))))


(call-command-with-output-error-to-string "echo 1; echo 2 >&2")
